/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/errno.h>
#include <linux/slab.h>

#include "mrp.h"
#include "mrp_private.h"
#include "mrp_timer.h"
#include "mrp_state_machine.h"
#include "mrp_periodic_transmission.h"

/**
*   PeriodicTransmission state table
*/
static const int mrp_periodic_trnasmission_state_table [MRP_PROTO_EVENT_NUM][MRP_PERIODIC_TRANSMISSION_STATE_NUM] =
{
	[MRP_PROTO_EVENT_Begin]
		= { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_PERIODIC_TRANSMISSION_STATE_ACTIVE, MRP_PROTO_ACTION_START_periodictimer),
			MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_PERIODIC_TRANSMISSION_STATE_ACTIVE, MRP_PROTO_ACTION_START_periodictimer) },
	[MRP_PROTO_EVENT_periodicEnable]
		= { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_PERIODIC_TRANSMISSION_STATE_NO_CHANGE),
			MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_PERIODIC_TRANSMISSION_STATE_ACTIVE, MRP_PROTO_ACTION_START_periodictimer) },
	[MRP_PROTO_EVENT_periodicDisable]
		= { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_PERIODIC_TRANSMISSION_STATE_PASSIVE),
			MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_PERIODIC_TRANSMISSION_STATE_NO_CHANGE) },
	[MRP_PROTO_EVENT_periodictimer]
		= { MRP_STATE_MACHINE_SET_UP_STATE_3 (MRP_PERIODIC_TRANSMISSION_STATE_ACTIVE, MRP_PROTO_ACTION_START_periodictimer, MRP_PROTO_ACTION_periodic),
			MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_PERIODIC_TRANSMISSION_STATE_NO_CHANGE) },
};

/**
*   Get the state based on the supplied event and state
*/
static int mrp_get_periodic_transmission_state (int mrp_protocol_event, int mrp_periodic_transmission_state, int * mrp_new_periodic_transmission_state)
{
	int res;

	if ( (res = mrp_get_new_state(mrp_periodic_trnasmission_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_periodic_transmission_state,
			MRP_PERIODIC_TRANSMISSION_STATE_MIN, MRP_PERIODIC_TRANSMISSION_STATE_MAX, MRP_PERIODIC_TRANSMISSION_STATE_NO_CHANGE)) >= 0)
	{
		*mrp_new_periodic_transmission_state = res;
		return 0;
	}

	return res;
}

/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_periodic_transmission_protocol_action1 (int mrp_protocol_event, int mrp_periodic_transmission_state, int * mrp_new_periodic_transmission_protocol_action)
{
	int res;

	if ( (res = mrp_get_new_protocol_action1 (mrp_periodic_trnasmission_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_periodic_transmission_state,
			MRP_PERIODIC_TRANSMISSION_STATE_MIN, MRP_PERIODIC_TRANSMISSION_STATE_MAX)) >= 0)
	{
		*mrp_new_periodic_transmission_protocol_action = res;
		return 0;
	}

	return res;
}

/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_periodic_transmission_protocol_action2 (int mrp_protocol_event, int mrp_periodic_transmission_state, int * mrp_new_periodic_transmission_protocol_action)
{
	int res;

	if ( (res = mrp_get_new_protocol_action2 (mrp_periodic_trnasmission_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_periodic_transmission_state,
			MRP_PERIODIC_TRANSMISSION_STATE_MIN, MRP_PERIODIC_TRANSMISSION_STATE_MAX)) >= 0)
	{
		*mrp_new_periodic_transmission_protocol_action = res;
		return 0;
	}

	return res;
}

static int mrp_periodic_transmission_start_periodictimer (struct mrp_periodic_transmission * mrp_periodic_transmission)
{
	mrp_periodic_transmission->periodictimer.expires = jiffies + MRP_TIMER_VALUE_PERIODIC_TIME_JIFFIES;
	add_timer (&(mrp_periodic_transmission->periodictimer));
	return 0;
}

static int mrp_periodic_transmission_perform_protocol_action (struct mrp_periodic_transmission * mrp_periodic_transmission, int protocol_action)
{
	switch (protocol_action)
	{
		case MRP_PROTO_ACTION_periodic: 			return mrp_perform_periodic_protocol_action 			(mrp_periodic_transmission->mrp);
		case MRP_PROTO_ACTION_START_periodictimer: 	return mrp_periodic_transmission_start_periodictimer 	(mrp_periodic_transmission);
		default:
		{
			MRP_PRINT_INFO ("%s %x\n", "UNSUPPORTED PROTO ACTION", protocol_action); return -EINVAL;
		}
	}
}

static struct mrp_state_machine_ops mrp_periodic_transmission_ops = {
	.mrp_state_machine_perform_protocol_action = (mrp_state_machine_perform_protocol_action) mrp_periodic_transmission_perform_protocol_action,
	.mrp_state_machine_get_new_state = mrp_get_periodic_transmission_state,
	.mrp_state_machine_get_new_protocol_action1 = mrp_get_periodic_transmission_protocol_action1,
	.mrp_state_machine_get_new_protocol_action2 = mrp_get_periodic_transmission_protocol_action2,
};

static void mrp_periodic_transmission_timer_function (unsigned long data)
{
	struct mrp_periodic_transmission * mrp_periodic_transmission;

	mrp_periodic_transmission = (struct mrp_periodic_transmission *) data;

	mrp_periodic_timer_expire_notify (mrp_periodic_transmission->mrp, mrp_periodic_transmission);
}

struct mrp_periodic_transmission * mrp_periodic_transmission_create_and_init (void)
{
	struct mrp_periodic_transmission * mrp_periodic_transmission;

	if ( 	! (mrp_periodic_transmission = kzalloc(sizeof(struct mrp_periodic_transmission), GFP_KERNEL))
		|| (mrp_state_machine_init (&mrp_periodic_transmission->mrp_state_machine, MRP_PERIODIC_TRANSMISSION_STATE_ACTIVE, &mrp_periodic_transmission_ops, "mrp_periodic_transmission")  )

		)
	{
		mrp_periodic_transmission_destroy (&mrp_periodic_transmission);
		return NULL;
	}

	setup_timer (&(mrp_periodic_transmission->periodictimer), mrp_periodic_transmission_timer_function, (unsigned long) mrp_periodic_transmission);

	return mrp_periodic_transmission;
}

void mrp_periodic_transmission_destroy (struct mrp_periodic_transmission ** mrp_periodic_transmission)
{
	struct mrp_periodic_transmission * p;

	p = *mrp_periodic_transmission;

	if (p)
	{
		del_timer_sync (&p->periodictimer);
		mrp_state_machine_clean_up (&p->mrp_state_machine);
		kfree (p);
		*mrp_periodic_transmission = NULL;
	}
}

int mrp_periodic_transmission_set_mrp (struct mrp_periodic_transmission * mrp_periodic_transmission, struct mrp * mrp)
{
	mrp_periodic_transmission->mrp = mrp;
	return 0;
}

void mrp_periodic_transmission_stop_timer(struct mrp_periodic_transmission * mrp_periodic_transmission)
{
    del_timer_sync (&mrp_periodic_transmission->periodictimer);
}
